//
// Created by root on 2019/9/26.
//

#include "AOAaudio.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

static const unsigned char HID_EVENT_RELEASE[] = {0x00, 0x00};

AOAaudio::AOAaudio(libusb_device *dev) :
        m_bQuit(false), m_AudioInterface(0), m_devHdl(nullptr), m_fdFile(0) {
    time_t nowTime = time(NULL);
    struct tm *timeInfo = localtime(&nowTime);
    char buf[128];
    strftime(buf, 128, "%Y_%m_%d%H_%M_%S.raw", timeInfo);
    if ((m_fdFile = fopen(buf, "w+")) == NULL) {
        throw string(buf) + "Cant open";
    }

    int8_t audioEp = 0;
    int16_t audioEpSz = 0;
    int ret = 0;
    uint8_t audioAlternateSetting = 0;
    libusb_config_descriptor *configDescriptor;
    libusb_interface const *anInterface;
    libusb_interface_descriptor const *interfaceDescriptor;
    libusb_endpoint_descriptor const *endpointDescriptor;
    ret = libusb_get_config_descriptor(dev, 0, &configDescriptor);
    if (ret < 0) {
        CHECKUSB(ret, "configure ");
        goto ERRNO;
    }
    for (int i = 0; i < configDescriptor->bNumInterfaces; ++i) {
        anInterface = &configDescriptor->interface[i];
        for (int j = 0; j < anInterface->num_altsetting; ++j) {
            interfaceDescriptor = &anInterface->altsetting[j];
            if (interfaceDescriptor->bInterfaceClass == 1 && \
                interfaceDescriptor->bInterfaceSubClass == 2 && \
                interfaceDescriptor->bNumEndpoints > 0) {
                for (int k = 0; k < interfaceDescriptor->bNumEndpoints; ++k) {
                    endpointDescriptor = &interfaceDescriptor->endpoint[k];
                    //    Transfer Type            Isochronous
                    //          Synch Type          Synchronous
                    //          Usage Type               Data
                    if (endpointDescriptor->bmAttributes == 13 && \
                        endpointDescriptor->bEndpointAddress & LIBUSB_ENDPOINT_IN) {
                        m_AudioInterface = interfaceDescriptor->bInterfaceNumber;
                        audioEp = endpointDescriptor->bEndpointAddress;
                        audioEpSz = endpointDescriptor->wMaxPacketSize;
                        audioAlternateSetting = interfaceDescriptor->bAlternateSetting;
                        goto OUT;
                    }
                }
            }
        }
    }

    OUT:
    if (!audioEpSz) {
        goto ERRNO;
    }


    ret = libusb_open(dev, &m_devHdl);
    if (ret < 0) {
        CHECKUSB(ret, "open device");
        goto ERRNO;
    }
    libusb_set_auto_detach_kernel_driver(m_devHdl, true);
    ret = libusb_claim_interface(m_devHdl, m_AudioInterface);
    CHECKUSB(ret, "   libusb_claim_interface faild ");
    ret = libusb_set_interface_alt_setting(m_devHdl, m_AudioInterface, audioAlternateSetting);
    CHECKUSB(ret, "   libusb_set_interface_alt_setting failed ");
    registerHid();

//    _startCapAudio();
    _startCapAudio(audioEp, audioEpSz);
//    m_thread = thread(&AOAaudio::writeCache2File, this);

    return;

    ERRNO:
    if (m_devHdl != nullptr) {
        libusb_close(m_devHdl);
    }
    throw string("INIT FAILED");
}

void isoCallback(struct libusb_transfer *xfer) {
    auto *audio = static_cast<AOAaudio *>( xfer->user_data );
    audio->_isoCallback(xfer);
}

void AOAaudio::writeCache2File() {
    while (!m_bQuit) {
        // todo we will do condition here
        if (!m_cache.empty()) {
            lock_guard <mutex> l(m_cacheLock);
//            fwrite(m_cache.data(), m_cache.size(), 1, m_fdFile);
            m_cache.clear();
        }
        sync();
//        sleep(1);
    }
}

//void AOAaudio::_startCapAudio() {
//    int ret;
//    int xfers = 16;
//    int pkSz = 8;
//    auto *buf = new uint8_t[xfers * pkSz * m_AudioEpSz];
//    for (int i = 0; i < xfers; ++i) {
//        libusb_transfer *transfer = libusb_alloc_transfer(pkSz);
//        libusb_fill_iso_transfer(transfer, m_devHdl, m_AudioEp,
//                                 buf + i * pkSz * m_AudioEpSz,
//                                 pkSz * m_AudioEpSz, pkSz, ::isoCallback, this, 0);
//        libusb_set_iso_packet_lengths(transfer, m_AudioEpSz);
//        ret = libusb_submit_transfer(transfer);
//        CHECKUSB(ret, "failed to transfer")
//        m_nTransfer.push_back(transfer);
//    }
//}

AOAaudio::~AOAaudio() {
    unRegisterHid();
    // release the transfer
    for (auto &transfer : m_nTransfer) {
        libusb_cancel_transfer(transfer);
        libusb_free_transfer(transfer);
    }
    // release the interface
    libusb_release_interface(m_devHdl, m_AudioInterface);
    libusb_release_interface(m_devHdl, m_AudioInterface);
    libusb_close(m_devHdl);
    // if debug here
    if (m_fdFile) {
        fclose(m_fdFile);
    }
    m_bQuit = true;
//    m_thread.join();
}

#define ACCESSORY_GET_PROTOCOL          51
#define ACCESSORY_SET_AUDIO_MODE        58
#define ACCESSORY_START                 53
#define ACCESSORY_REGISTER_HID        54
#define ACCESSORY_UNREGISTER_HID        55
#define ACCESSORY_SET_HID_REPORT_DESC   56
#define ACCESSORY_SEND_HID_EVENT        57

#define HID_EVENT_LEN                   2
#define CONSUMER_HID_DESC_LEN           23

#define FIRST_REQUEST_TYPE              0xC0
#define ANDROID_REQUEST_TYPE            0x40


static unsigned char consumer_hid_descriptor[CONSUMER_HID_DESC_LEN] = {
        0x05, 0x0c,                     // Usage Page (Consumer Devices)
        0x09, 0x01,                     // Usage (Consumer Control)
        0xa1, 0x01,                     // Collection (Application)
        0x19, 0x00,                     // Usage Minimum (0),
        0x2A, 0x3C, 0x02,               // Usage Maximum (0x23C),
        0x15, 0x00,                     // Logical Minimum (0)
        0x26, 0x3C, 0x02,               // Logical Maximum (0x23C)
        0x75, 0x10,                     // Report Size (10)
        0x95, 0x01,                     // Report Count (1)
        0x81, 0x00,                     // Input (Data,Array,Absolute)
        0xC0                            // End Collection
};

void AOAaudio::registerHid() {
    int ret;
    ret = libusb_control_transfer(m_devHdl, ANDROID_REQUEST_TYPE,
                                  ACCESSORY_REGISTER_HID, 1, CONSUMER_HID_DESC_LEN, NULL, 0, 0);
    CHECKUSB(ret, "register hid");
    ret = libusb_control_transfer(m_devHdl, ANDROID_REQUEST_TYPE,
                                  ACCESSORY_SET_HID_REPORT_DESC, 1, 0, consumer_hid_descriptor,
                                  CONSUMER_HID_DESC_LEN, 0);
    CHECKUSB(ret, "register ACCESSORY_SET_HID_REPORT_DESC");
}

void AOAaudio::unRegisterHid() {
    int ret;
    ret = libusb_control_transfer(m_devHdl, ANDROID_REQUEST_TYPE,
                                  ACCESSORY_UNREGISTER_HID, 1, 0, NULL, 0, 0);
    CHECKUSB(ret, "unregister hid");
}

int AOAaudio::sendKey(const uint8_t *event, uint nSz) {
    int ret = libusb_control_transfer(m_devHdl, ANDROID_REQUEST_TYPE,
                                      ACCESSORY_SEND_HID_EVENT, 1, 0, (unsigned char *) event, nSz, 0);
    if (ret < 0)
        return false;
    ret = libusb_control_transfer(m_devHdl, ANDROID_REQUEST_TYPE,
                                  ACCESSORY_SEND_HID_EVENT, 1, 0, (unsigned char *) HID_EVENT_RELEASE, HID_EVENT_LEN,
                                  0);
    return ret > 0;
}

void AOAaudio::_isoCallback(libusb_transfer *pTransfer) {

    switch (pTransfer->status) {
        case LIBUSB_TRANSFER_NO_DEVICE:
            printf("no device \n");
            break;
        case LIBUSB_TRANSFER_COMPLETED: {
            int ret = 0;
            int audioEpSz = pTransfer->iso_packet_desc[0].length;
            {
                lock_guard <mutex> l(m_cacheLock);
                for (int i = 0; i < pTransfer->num_iso_packets; ++i) {
                    uint8_t *pbuf = pTransfer->buffer + i * audioEpSz;
                    for (uint j = 0; j < pTransfer->iso_packet_desc[i].actual_length; ++j) {
                        // todo  optimze this
                        m_cache.emplace_back(pbuf[j]);
                    }
                }
//                cout <<pTransfer->num_iso_packets <<"\t"<< pTransfer->iso_packet_desc[0].length <<"\t"<< m_AudioEpSz<<endl ;
            }
            ret = libusb_submit_transfer(pTransfer);
            CHECKUSB(ret, "submit transfer ");
//            printf("received %d \n", pTransfer->iso_packet_desc[0].actual_length);
            break;
        }
        case LIBUSB_TRANSFER_CANCELLED:
            break;
        default:
            printf("iso transform %d \n", pTransfer->status);
    }
}

bool AOAaudio::isEqual(libusb_device *other) {
    libusb_device *my = libusb_get_device(m_devHdl);
    return my == other;
}

void AOAaudio::_startCapAudio(uint8_t audioEp, uint16_t audioEpSz) {
    int ret;
    int xfers = 16;
    int pkSz = 8;
    auto *buf = new uint8_t[xfers * pkSz * audioEpSz];
    for (int i = 0; i < xfers; ++i) {
        libusb_transfer *transfer = libusb_alloc_transfer(pkSz);
        libusb_fill_iso_transfer(transfer, m_devHdl, audioEp,
                                 buf + i * pkSz * audioEpSz,
                                 pkSz * audioEpSz, pkSz, ::isoCallback, this, 0);
        libusb_set_iso_packet_lengths(transfer, audioEpSz);
        ret = libusb_submit_transfer(transfer);
        CHECKUSB(ret, "failed to transfer")
        m_nTransfer.push_back(transfer);
    }

}
